#include "tcpSocket.h"

#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sstream>
#include <string.h>
#include <fcntl.h>
#include <iostream>
#include <cstdio>
#include <sys/select.h>
#include "log.h"
#include "macro.h"

sylar::Logger::ptr g_logger = DANIEL_LOG_NAME("g_logger");

namespace daniel
{
    //创建连接的cfd    供客户端使用TcpSocket初始化方式，需要后续调用connectToHost
    TcpSocket::TcpSocket()
    {
    }
    //用于服务端，已经有fd，使用TcpSocket的sendMsg和recvMsg功能
    //不能调用connectToHost方法
    TcpSocket::TcpSocket(int fd) : m_connectfd(fd)
    {
    }
    //释放文件描述符
    TcpSocket::~TcpSocket()
    {
        std::cout << "TcpSocket正在析构!" << std::endl;
        if (m_connectfd != -1)
        {
            close(m_connectfd);
            m_connectfd = -1;
        }
    }
    //连接客户端
    /*设置connect()函数的非阻塞：
        1.设置connect函数操作的文件描述符为非阻塞；
        2.调用connect();
        3.使用select进行检测    需要使用getsockopt()进行判断；
        4.设置connect()函数操作的文件描述符进行判断；
    */
    void TcpSocket::connectToHost(const std::string &ip, uint16_t port)
    {
        m_connectfd = socket(AF_INET, SOCK_STREAM, 0);
        if (m_connectfd < 0)
        {
            throw("create socket failed!");
        }
        //需要服务端的ip地址和端口
        struct sockaddr_in sockAddr;
        sockAddr.sin_family = AF_INET;
        inet_pton(AF_INET, ip.c_str(), (void *)&sockAddr.sin_addr.s_addr);
        sockAddr.sin_port = htons(port);
        //将文件描述符设置为非阻塞之后，connect函数不再阻塞
        setNonblock(m_connectfd);
        int ret = isConnectReady(m_connectfd, (struct sockaddr *)&sockAddr, sizeof(sockAddr),5000);
        DANIEL_ASSERT2(ret==0,"connect to host failed!");
    }
    void TcpSocket::disconnect()
    {
        close(m_connectfd);
        m_connectfd = -1;
        return;
    }
    //如果接受的string过大，会导致内容占满缓冲区；
    //修改使其单次只接收4096个字符，如果要接收多个字符，用户自己处理
    void TcpSocket::recvMsg(std::string& recvMsg)
    {
        recvMsg.clear();
        //要进行拆包处理
        int32_t length;
        //先读取4个字节
        int ret = isReadyRead(m_connectfd, 5000);
        if (ret > 0)
        {
            recv(m_connectfd, (void *)&length, 4, 0);
            length = ntohl(length);
            char *buf = (char *)malloc(length);
            ret = readNByte(buf, length);
            recvMsg.assign(buf,length);
            // if (ret > 0)
            // {
            //     for(int i=0;i<length;i++){
            //         recvMsg.push_back(buf[i]);
            //     }
            // }
            free(buf);
        }
    }
    //将msg发送出去，需要防止TCP粘包，添加文件头
    //在文件头部，添加4个字节，用于说明整个文件的大小
    //接收方通过文件的大小进行拆包
    void TcpSocket::sendMsg(const std::string &msg)
    {
        int32_t length = msg.size();
        char *buf = (char *)malloc(length + 4);
        int32_t net_length = htonl(length);
        //将长度放入到  消息的头部  再放入消息
        memcpy(buf, (void *)&net_length, 4);
        memcpy(buf + 4, (void *)msg.c_str(), length);
        //检测是否可写，可写之后，调用writeN发送消息
        int ret = isReadyWrite(m_connectfd, 5000);
        if (ret > 0)
        {
            //错误由writeNByte内部进行处理
            writeNByte(buf, length + 4);
        }
        free(buf);
    }

    void TcpSocket::setNonblock(int fd)
    {
        if (fd < 0)
        {
            std::cout << "fd can't less than 0" << std::endl;
            return;
        }
        int flags = fcntl(fd, F_GETFD);
        flags |= O_NONBLOCK;
        fcntl(fd, F_SETFD, flags);
        return;
    }
    //设置文件描述符为阻塞
    void TcpSocket::setBlock(int fd)
    {
        if (fd < 0)
        {
            std::cout << "fd can't less than 0" << std::endl;
            return;
        }
        int flags = fcntl(fd, F_GETFD);
        //一种尝试，按照道理是这样的；
        flags &= ~O_NONBLOCK;
        fcntl(fd, F_SETFD, flags);
        return;
    }
    //从文件描述符一次性读入n个byte的字符到dest中
    // dest是在堆区中开辟的，大小为n个字节的空间
    int32_t TcpSocket::readNByte(char *dest, int32_t n)
    {
        return rdWrNByte(dest, n, ReadEvent);
    }
    //一次性读取n个byte到缓冲区，缓冲区的大小由用户开辟和释放
    int32_t TcpSocket::writeNByte(char *src, int32_t n)
    {
        return rdWrNByte(src, n, WriteEvent);
    }
    // type使用const修饰，防止在函数中被篡改
    int32_t TcpSocket::rdWrNByte(char *buf, int32_t n, const TcpSocket::EventType &type)
    {
        int32_t ret = 0;
        int32_t nleft = n;
        char *temp = buf;
        while (nleft > 0)
        {
            if (type == ReadEvent)
            {
                ret = recv(m_connectfd, temp, nleft, 0);
            }
            else if (type == WriteEvent)
            {
                ret = send(m_connectfd, temp, nleft, 0);
            }
            if (ret == 0)
            {
                close(m_connectfd);
                m_connectfd = -1;
                DANIEL_LOG_DEBUG(g_logger) << "connection lost! ";
                return ret;
            }
            else if (ret < 0)
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    continue;
                }
                DANIEL_LOG_DEBUG(g_logger) << "send msg error!";
                return ret;
            }
            else
            {
                // DANIEL_LOG_DEBUG(g_logger)<<"recv msg success!";
                temp += ret;
                nleft -= ret;
            }
        }
        return ret;
    }

    int32_t TcpSocket::isReadyRead(int32_t fd, int32_t timeout)
    {
        return isFDReady(fd, timeout, ReadEvent);
    }
    int32_t TcpSocket::isReadyWrite(int32_t fd, int32_t timeout)
    {
        return isFDReady(fd, timeout, WriteEvent);
    }

    int32_t TcpSocket::isFDReady(int32_t fd, int32_t timeout, const TcpSocket::EventType &type)
    {
        fd_set fds;
        FD_ZERO(&fds);
        FD_SET(fd, &fds);
        struct timeval timeVal;
        timeVal.tv_sec = 0;
        timeVal.tv_usec = timeout * 1000;
        int ret = 0;
        while (1)
        {

            if (type == ReadEvent)
            {
                ret = select(fd + 1, &fds, nullptr, nullptr, &timeVal);
            }
            else if (type == WriteEvent)
            {
                ret = select(fd + 1, nullptr, &fds, nullptr, &timeVal);
            }
            //发生错误   返回-1
            if (ret < 0)
            {
                //捕捉被系统信号终端的情况
                if (errno == EINTR)
                {
                    continue;
                }
                else
                {
                    return ret;
                }
            } //连接超时
            else if (ret == 0)
            {
                return ret;
            }
            else
            {
                return ret;
            }
        }
    }
    int32_t TcpSocket::isConnectReady(int fd, struct sockaddr *addr, int length, int32_t timeout)
    {
        setNonblock(fd);
        //connect非阻塞，直接返回
        connect(fd,addr,sizeof(struct sockaddr_in));
        fd_set wfds;
        FD_SET(fd, &wfds);
        struct timeval timeVal;
        timeVal.tv_sec = 0;
        timeVal.tv_usec = timeout * 1000;
        int ret = select(fd + 1, nullptr, &wfds, nullptr, &timeVal);
        if (ret > 0)
        {
            int sockoptRet;
            int err;
            socklen_t sockLen = sizeof(err);
            //将连接状态的错误代码，通过err传出
            sockoptRet = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &sockLen);
            if (sockoptRet == -1)
            {
                return -1;
            }
            //成功建立连接
            if (err == 0){
                ret = 0;
            }
            else{
                errno=err;
                return -1;
            }
        }
        else if (ret == 0)
        {
            //说明超时
            return -1;
        }
        // ret<0，调用select出错
        else
        {
            return -1;
        }
        //将connect重新设置为阻塞
        setBlock(fd);
        return 0;
    }
    struct HostInfoInit
    {
        HostInfoInit()
        {
            daniel::ConfigMsg::GetInstance()->lookUp("hostinfo",HostInfo(),"hostinfo");
        }
    };
    static HostInfoInit hostinfoInit;
}